﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
using System.Globalization;


namespace Tesla.Util.Utilities
{

    public static class GenericClassFactory<TKey, TBaseType> where TBaseType : class
    {
        // Declare the delegate    
        private delegate TBaseType BaseTypeInvoker();
        // The HashTable that caches the delegates    
        private static Dictionary<TKey, BaseTypeInvoker> _delegates = new Dictionary<TKey, BaseTypeInvoker>();
        // The object that will be used for ensuring thread-safety    
        private static object _locker = new object();

        private static BaseTypeInvoker CreateInvoker(Type t)
        {
            // Get the Default constructor.        
            ConstructorInfo ctor = t.GetConstructor(new Type[0]);
            // Check if the constructor exists.        
            if (ctor == null)
                throw new ArgumentException(  String.Format(CultureInfo.InvariantCulture, "{0} doesn't have a public default constructor.", t.FullName));
            // Create a new method.        
            DynamicMethod dm = new DynamicMethod(t.Name + "Ctor", t, new Type[0], typeof(TBaseType).Module);
            // Generate the intermediate language.        
            ILGenerator lgen = dm.GetILGenerator();
            lgen.Emit(OpCodes.Newobj, ctor);
            lgen.Emit(OpCodes.Ret);
            // Finish the method and create new delegate        // pointing at it.        
            return (BaseTypeInvoker)dm.CreateDelegate(typeof(BaseTypeInvoker));
        }


        public static void Add(TKey key, Type type)
        {
            // Some checks on the type argument        
            if (type == null)
                throw new ArgumentNullException("type");
            // Check if object is not a class        
            if (type.IsClass == false)
                throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "{0} is not a reference type.", type.FullName), "type");
            // Check if object is abstract        
            if (type.IsAbstract == true)
                throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "{0} is an abstract class, which can not be created.", type.FullName), "type");
            // Check whether the given type is assignable from        
            if (typeof(TBaseType).IsAssignableFrom(type) == false)
                throw new ArgumentException(String.Format("The given type {0} should be derivable from {1}.", type.FullName, typeof(TBaseType).FullName), "type");
            // Lock for thread safety        
            lock (_locker)
            {
                if (_delegates.ContainsKey(key) == false)
                {
                    try
                    {
                        // Create the delegate for the type
                        BaseTypeInvoker invoke = CreateInvoker(type);
                        // Try to invoke function (extra error check,                     
                        // so the delegate is not added on error)                    
                        invoke();
                        // The invoker executed correctly (no exceptions)                    
                        // so let's add it to the dictionary                    
                        _delegates.Add(key, invoke);
                    }
                    catch (InvalidCastException)
                    {
                        throw new InvalidCastException(String.Format(CultureInfo.InvariantCulture, "{0} couldn't be casted to {1}.", type.FullName, typeof(TBaseType).FullName));
                    }
                }
            }
        }

        public static TBaseType CreateInstance(TKey key) 
        { 
            BaseTypeInvoker invoke = null; 
            lock (_locker) 
            { 
                _delegates.TryGetValue(key, out invoke); 
            } 
            return invoke != null ? invoke() : null; 
        }

    }
    

    
    
}
